/************************************************************************
 * SDL_Layer - A layered display mechanism for SDL
 * Copyright (C) 2008  Julien CLEMENT
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ************************************************************************/

#include "SDL_Layer.h"
#include "SDL_Layer_internal.h"
#include <stdio.h>

static void __SDLayer_InitLayer(SDLayer * layer, Uint32 flags, float sfactor,
		int width, int height, int bitsPerPixel, Uint32 Rmask, Uint32 Gmask,
		Uint32 Bmask, Uint32 Amask) {
	SDL_Surface * surf;

	surf = SDL_CreateRGBSurface(flags, width, height, bitsPerPixel, Rmask,
			Gmask, Bmask, Amask);

	if (surf == (SDL_Surface *) 0) {
		fprintf(stderr,
				"Cannot allocate memory for a new SDL_Surface with properties:\n"
					"%dx%d@%d bpp (masks: %d,%d,%d,%d)\n"
					"SDL reports: %s\n", width, height, bitsPerPixel, Rmask,
				Gmask, Bmask, Amask, SDL_GetError());

		return;
	}

	/* Convert the layer surface to the display format */
	layer->surface = SDL_DisplayFormat(surf);

	if (layer->surface == (SDL_Surface *) 0) {
		fprintf(stderr,
				"Cannot convert the layer surface to the display format.\n"
					"SDL reports: %s\n", SDL_GetError());
		SDL_FreeSurface(surf);

		return;
	}

	SDL_FreeSurface(surf);
	surf = (SDL_Surface *) 0;

	layer->rect.x = 0;
	layer->rect.y = 0;
	layer->rect.w = width;
	layer->rect.h = height;

	layer->region = NULL;
	layer->visible = 1;
	layer->sfactor = sfactor;

	/* No dirty rectangles */
	layer->rects = (SDL_Rect *) 0;
	layer->n_rects = 0;
	layer->n_chunks = 0;

	return;
}

static void __SDLayer_ClearLayer(SDLayer * layer) {
	/* The dirty rectangles are automatically discarded
	 * by SDLayer_Update(), called at the beginning of
	 * SDLayer_FreeLayeredDisplay()
	 */
	SDL_FreeSurface(layer->surface);
	layer->surface = (SDL_Surface *) 0;
}

void SDLayer_DiscardRects(SDLayer * l) {

	l->n_rects = 0;
	l->n_chunks = 0;

	free(l->rects);
	l->rects = (SDL_Rect *) 0;

}

SDLayer_Display *
SDLayer_CreateRGBLayeredDisplay(Uint32 flags, int refresh_method, int n_layers,
		int * widths, int * heights, int bitsPerPixel, Uint32 Rmask,
		Uint32 Gmask, Uint32 Bmask, Uint32 Amask) {

	int k;

	SDLayer_Display * ldisplay = (void *) 0;
	SDL_Surface *display;

	display = SDL_GetVideoSurface();

	if (display == (SDL_Surface *) 0) {
		fprintf(stderr,
				"Error (from %s): You must initialize a video surface before\n"
					"creating a new layered display.\n", __func__);
		exit(-1);
	}

	ldisplay = (SDLayer_Display *) calloc(1, sizeof(SDLayer_Display));

	if (ldisplay == (SDLayer_Display *) 0) {
		fprintf(
				stderr,
				"Error (from %s): Cannot allocate memory for a new SDLayer_Display.\n",
				__func__);
		exit(-1);
	}

	/* Store a pointer to the video surface to avoid
	 * calling SDL_GetVideoSurface() each time ... */
	ldisplay->display = display;

	ldisplay->layers = (SDLayer *) calloc(n_layers, sizeof(SDLayer));

	if (ldisplay->layers == (SDLayer *) 0) {
		fprintf(stderr,
				"Error (from %s): Cannot allocate memory for %d layers\n",
				__func__, n_layers);
		free(ldisplay);
		exit(-1);
	}

	ldisplay->n_layers = n_layers;

	for ( k = 0; k < n_layers; ++k )
		/* Default scrolling factor is 1.0 */
		__SDLayer_InitLayer(&LAYER(ldisplay,k), flags, 1.0f, widths[k],
				heights[k], bitsPerPixel, Rmask, Gmask, Bmask, Amask);

	/* Default viewport is aligned with the top-left corner
	 * of the display.
	 */
	ldisplay->viewport.x = 0;
	ldisplay->viewport.y = 0;
	ldisplay->viewport.w = display->w;
	ldisplay->viewport.h = display->h;

	/* Whole refresh is needed for the first blit */
	ldisplay->force_whole_refresh = 1;

	/* No need to refresh when nothing is blit yet */
	ldisplay->need_refresh = 0;

	/* Set the refresh method to use */
	ldisplay->refresh_method = refresh_method;

	/* Set the origin to (0,0) */
	ldisplay->origin_x = 0;
	ldisplay->origin_y = 0;

	return ldisplay;

}

void SDLayer_FreeLayeredDisplay(SDLayer_Display * ldisplay) {

	int k;

	/* Perform a last update of the display, which will
	 * discard all the dirty rectangles.
	 */
	SDLayer_Update(ldisplay);

	for ( k = 0; k < ldisplay->n_layers; ++k ) {
		__SDLayer_ClearLayer (&LAYER(ldisplay,k));
		/* Clear any region set on each layer */
		SDLayer_UnsetRegion(ldisplay, k);
	}

	free(ldisplay->layers);
	ldisplay->layers = (SDLayer *) 0;

	/* By security */
	ldisplay->n_layers = 0;
}

void SDLayer_SetColorKey(SDLayer_Display * ldisplay, Uint32 flags, Uint32 key) {

	int k;

	for ( k = 0; k < ldisplay->n_layers; ++k )
		SDL_SetColorKey (LAYER(ldisplay,k).surface, flags, key);

}

SDL_Surface * SDLayer_GetLayer(SDLayer_Display * ldisplay, int layer_number) {

#ifdef SDLAYER_CHECK_PARAMETERS
	SDLAYER_CHECK_LAYER_INDICE(ldisplay,layer_number);
#endif

	return LAYER(ldisplay,layer_number).surface;

}

void SDLayer_ForceWholeRefresh(SDLayer_Display * ldisplay) {

	ldisplay->force_whole_refresh = 1;
	ldisplay->need_refresh = 1;

}

void SDLayer_Fill(SDLayer_Display * ldisplay, Uint32 color) {
	int k;

	for ( k = 0; k < ldisplay->n_layers; ++k )
		SDL_FillRect (LAYER(ldisplay,k).surface,
			      &LAYER(ldisplay,k).rect,
			      color);

	SDLayer_ForceWholeRefresh(ldisplay);
}

void SDLayer_SetScrollingFactor(SDLayer_Display * ldisplay, int layer_number,
		float sfactor) {

#ifdef SDLAYER_CHECK_PARAMETERS
	SDLAYER_CHECK_LAYER_INDICE(ldisplay, layer_number);
#endif

	LAYER(ldisplay,layer_number).sfactor = sfactor;

}

void SDLayer_SetViewport(SDLayer_Display * ldisplay, Sint16 x, Sint16 y) {

	SDL_Rect * rect;

	rect = &(ldisplay->viewport);

	/* Check if there were changes */
	if ((rect->x == x) && (rect->y == y))
		return;

	rect->x = x;
	rect->y = y;

	/* Request a whole refresh */
	SDLayer_ForceWholeRefresh(ldisplay);

}

void SDLayer_SetRegion(SDLayer_Display * layered_display, int layer_number,
		SDL_Rect * region) {
	SDLayer * layer = &LAYER(layered_display, layer_number);

	/* Free any existing region */
	if (layer->region != NULL) {
		free(layer->region);
		layer->region = NULL;
	}

	/* Create a new region */
	layer->region = (SDL_Rect *) calloc(1, sizeof(SDL_Rect));

	layer->region->x = region->x;
	layer->region->y = region->y;
	layer->region->w = region->w;
	layer->region->h = region->h;

	/* Set a clipping rectangle */
	SDL_SetClipRect(layer->surface, layer->region);

	/* Force a whole refresh */
	SDLayer_ForceWholeRefresh(layered_display);
}

void SDLayer_UnsetRegion(SDLayer_Display * layered_display, int layer_number) {
	SDLayer * layer = &LAYER(layered_display,layer_number);

	if (layer->region != NULL) {
		free(layer->region);
		layer->region = NULL;
		/* Remove the clipping rectangle */
		SDL_SetClipRect(layer->surface, NULL);

		/* Force a whole refresh */
		SDLayer_ForceWholeRefresh(layered_display);
	}
}

void SDLayer_SetDisplayRect(SDLayer_Display * layered_display, SDL_Rect *rect) {
	SDL_Rect * vp;

	vp = &(layered_display->viewport);

	/* First, affect the viewport size */
	vp->w = rect->w;
	vp->h = rect->h;

	/* Then, remember the translation vector */
	layered_display->origin_x = rect->x;
	layered_display->origin_y = rect->y;

	/* Whenever the display rectangle changes, we need to refresh the whole screen */
	SDLayer_ForceWholeRefresh(layered_display);
}

void SDLayer_UnsetDisplayRect(SDLayer_Display * layered_display) {
	SDL_Rect rect;

	// Whole screen area
	rect.x = 0;
	rect.y = 0;
	rect.w = layered_display->display->w;
	rect.h = layered_display->display->h;

	SDLayer_SetDisplayRect(layered_display, &rect);
}

void SDLayer_SetVisible(SDLayer_Display * ldisplay, int n, int toggle) {

#ifdef SDLAYER_CHECK_PARAMETERS
	SDLAYER_CHECK_LAYER_INDICE(ldisplay, n);
#endif

	if (((toggle) && (LAYER(ldisplay,n).visible == 1)) || ((!toggle)
			&& (LAYER(ldisplay,n).visible == 0))) {
		/* No change */
		printf("No change !\n");
		return;
	}

	/* Change visibility */
	if (toggle)
		LAYER(ldisplay,n).visible = 1;
	else
		LAYER(ldisplay,n).visible = 0;

	/* Force whole refresh */
	SDLayer_ForceWholeRefresh(ldisplay);

}

void SDLayer_ToggleFullScreen(SDLayer_Display *ldisplay) {
	Uint32 flags;
	SDL_Rect clip;
	SDL_Surface * disp = ldisplay->display;
	int w, h, bpp;

	/* Safety check */
	if (ldisplay->display == (SDL_Surface *) 0) {
		printf("Unexpected error: display is NULL\n");
		exit(-1);
	}

	w = ldisplay->display->w;
	h = ldisplay->display->h;
	bpp = ldisplay->display->format->BitsPerPixel;
	flags = ldisplay->display->flags;

	/* Switch back to windowed mode */
	if (flags & SDL_FULLSCREEN)
		flags &= ~SDL_FULLSCREEN;
	/* Switch to fullscreen mode */
	else
		flags |= SDL_FULLSCREEN;

	/* Initialize a new display surface */

	/* Copy the clipping rectangle */
	clip.x = disp->clip_rect.x;
	clip.y = disp->clip_rect.y;
	clip.w = disp->clip_rect.w;
	clip.h = disp->clip_rect.h;

	ldisplay->display = SDL_SetVideoMode(w, h, bpp, flags);

	if (ldisplay->display == (SDL_Surface *) 0) {
		if (flags & SDL_FULLSCREEN) {
			printf("Failed to switch to fullscreen mode !\n");
			// Cancel fullscreen mode
			flags &= ~SDL_FULLSCREEN;
		} else {
			printf("Failed to switch to windowed mode !\n");
			// Cancel windowed mode
			flags |= SDL_FULLSCREEN;
		}
		ldisplay->display = SDL_SetVideoMode(w, h, bpp, flags);
		if (ldisplay->display == (SDL_Surface *) 0) {
			printf("Unexpected error: video surface is NULL\n");
			exit(-1);
		}
		return;
	}

	SDL_SetClipRect(ldisplay->display, &clip);
	SDLayer_ForceWholeRefresh(ldisplay);
}

void SDLayer_GetLayerCoordinates(SDLayer_Display * ld, Sint16 * layer_x,
		Sint16 * layer_y, Sint16 x, Sint16 y, int layer) {

	float sfactor;
	Sint16 vpx, vpy;

	sfactor = ld->layers[layer].sfactor;
	vpx = ld->viewport.x;
	vpy = ld->viewport.y;

	/* Translate using the display rectangle */
	*layer_x = x - ld->origin_x;
	*layer_y = y - ld->origin_y;

	/* Then calculate the position in the layer */
	*layer_x += (Sint16)((float) vpx * sfactor);
	*layer_y += (Sint16)((float) vpy * sfactor);

}

